<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<style id="testsheet"></style>
<div id="t1">
    <span></span>
</div>
<div id="t2">
    <span></span>
</div>
<div id="t3">
    <div class="second"></div>
    <div></div>
    <div></div>
    <div></div>
</div>
<div id="t4">
    <div></div>
    <div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div id="t4_green1"></div>
    </div>
    <div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div></div>
        <div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>
        <div></div>
        <div></div>
        <div></div>
        <div id="t4_green2"></div>
    </div>
</div>
<div id="t5">
    <div></div>
    <div>
        <div>
            <div></div>
            <div></div>
            <div class="descendant"></div>
            <div></div>
        </div>
        <div></div>
    </div>
    <div></div>
    <div></div>
</div>
<div id="t6">
    <div></div>
    <div><div class="descendant"></div></div>
    <div id="nth"><div class="descendant"></div></div>
    <div class="sibling"><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
    <div><div class="descendant"></div></div>
</div>

<script>
    function setSelectorForTest(selector) {
        testsheet.innerText = selector + " { background-color: green }";
    }

    function backgroundIsGreen(element) {
        assert_equals(getComputedStyle(element).backgroundColor, "rgb(0, 128, 0)");
    }

    function backgroundIsTransparent(element) {
        assert_equals(getComputedStyle(element).backgroundColor, "rgba(0, 0, 0, 0)");
    }

    setSelectorForTest("#t1 > span:nth-child(even)");

    test(() => {
        t1.offsetTop;
        assert_equals(t1.lastChild.nodeType, Node.TEXT_NODE);
        t1.insertBefore(document.createElement("span"), t1.lastChild);
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 1);
    }, "Appending an element sibling should not affect :nth-child of preceding siblings.");

    setSelectorForTest("#t2 > span:nth-last-child(even)");

    test(() => {
        t2.offsetTop;
        assert_equals(t2.firstChild.nodeType, Node.TEXT_NODE);
        assert_equals(t2.firstChild.nextSibling.nodeType, Node.ELEMENT_NODE);
        t2.insertBefore(document.createElement("span"), t2.firstChild.nextSibling);
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 1);
    }, "Prepending an element sibling should not affect :nth-last-child of succeeding siblings.");

    setSelectorForTest("#t3 > .second:nth-child(2)");

    test(() => {
        t3.offsetTop;
        let second = t3.querySelector(".second");
        backgroundIsTransparent(second);
        t3.insertBefore(document.createElement("div"), t3.firstChild);
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 2);
        backgroundIsGreen(second);
    }, "Prepending an element sibling causing :nth-child class invalidation.");

    setSelectorForTest("#t4 div:nth-of-type(10)");

    test(() => {
        t4.offsetTop;
        backgroundIsGreen(t4_green1);
        backgroundIsGreen(t4_green2);
    }, "div:nth-of-type(10) matching 10th div sibling.");

    test(() => {
        t4.firstElementChild.remove();
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 2);
    }, "div:nth-of-type(10) invalidating all siblings, but no descendants, on removal.");

    test(() => {
        t4.insertBefore(document.createElement("div"), t4.firstChild);
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 3);
    }, "div:nth-of-type(10) invalidating all siblings, but no descendants, on insertion.");

    test(() => {
        t4.lastElementChild.firstElementChild.remove();
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 9);
        backgroundIsTransparent(t4_green2);
    }, "div:nth-of-type(10) changing style on sibling removal.");

    test(() => {
        t4.lastElementChild.insertBefore(document.createElement("div"), t4.lastElementChild.firstChild);
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 10);
        backgroundIsGreen(t4_green2);
    }, "div:nth-of-type(10) changing style on sibling insertion.");

    setSelectorForTest("#t5 div:nth-of-type(2) .descendant");
    let descendant = t5.querySelector(".descendant");

    test(() => {
        t5.offsetTop;
        backgroundIsGreen(descendant);

        t5.firstElementChild.remove();
        // The expected count should be 1, but it's actually 4 due to
        // "summary:fist-of-type" in the UA stylesheet.
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 4);
        backgroundIsTransparent(descendant);
    }, "Removing nth-sibling affecting targeted descendant.");

    test(() => {
        t5.insertBefore(document.createElement("div"), t5.firstChild);
        // The expected count should be 2, but it's actually 5 due to
        // "summary:fist-of-type" in the UA stylesheet.
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 5);
        backgroundIsGreen(descendant);
    }, "Inserting nth-sibling affecting targeted descendant.");

    // The #nth id is necessary to avoid UniversalSiblingInvalidationSet
    // invalidating all siblings on insertion/removal, because those
    // invalidation sets are still scheduled on the parent node as descedant
    // invalidations.
    setSelectorForTest("#t6 #nth:nth-of-type(2n) + .sibling .descendant");
    descendant = t6.querySelector(".sibling .descendant");

    test(() => {
        t6.offsetTop;
        backgroundIsTransparent(descendant);

        t6.firstElementChild.remove();
        // The expected count should be 1, but it's actually 2 due to
        // "summary:fist-of-type" in the UA stylesheet.
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 2);
        backgroundIsGreen(descendant);
    }, "Removing nth-sibling affecting targeted descendant with sibling invalidation.");

    test(() => {
        t6.insertBefore(document.createElement("div"), t6.firstChild);
        // The expected count should be 2, but it's actually 3 due to
        // "summary:fist-of-type" in the UA stylesheet.
        assert_equals(internals.updateStyleAndReturnAffectedElementCount(), 3);
        backgroundIsTransparent(descendant);
    }, "Inserting nth-sibling affecting targeted descendant with sibling invalidation.");
</script>
